package com.smssalama.storage.framework;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.Hashtable;

/**
 * Base class for objects that can be stored
 * 
 * @author Arnold P. Minde
 */
public abstract class Storable {
	protected static final Hashtable classMap = new Hashtable();

	int recordId;

	public int getRecordId() {
		return this.recordId;
	}

	protected static void registerStorable(String name, Class clazz) {
		Storable.classMap.put(name, clazz);
	}

	/** Gives a chance to the object to strore its properties */
	protected abstract void store(DataOutputStream stream) throws IOException,
			ObjectStorageException;

	/**
	 * Called when the object needs to be reloaded from stored data.
	 * 
	 * {@link EOFException} is ignored. 
	 * It is assumed that this will only happen if the data was saved by an earlier version of the object, 
	 * and the new version has additional fields.
	 * 
	 * @param stream
	 * @throws IOException
	 * @throws ObjectStorageException
	 */
	protected abstract void load(DataInputStream stream) throws IOException,
			ObjectStorageException;

	/** Returns this object in serialized form */
	public final byte[] getBytes() throws ObjectStorageException {
		try {
			ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
			DataOutputStream stream = new DataOutputStream(byteStream);
			// stream.writeUTF(ClassResolver.getClassId(this.getClass()));
			String className = this.getClass().getName();
			// System.out.println("Class: "+className);
			if (className.length() == 0) {
				throw new RuntimeException("className is empty ");
			}
			stream.writeUTF(className);
			store(stream);
			return byteStream.toByteArray();
		} catch (Exception ex) {
			throw new ObjectStorageException(ex.getMessage(), ex);
		}
	}

	/** Construct a Storable object from a serialized form */
	public static Storable init(byte[] data) throws ObjectStorageException {
		// Storable.dump('B', 0, data);
		ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
		DataInputStream stream = new DataInputStream(byteStream);
		String className = null;
		try {
			className = stream.readUTF();
			// Class clazz = ClassResolver.resolveClass(classId);
			if (className.length() == 0) {
				throw new RuntimeException("className is empty " + data.length);
			}
			System.out.println("Class Read: " + className);
			Class klass = Class.forName(className);
			Storable storable = (Storable) klass.newInstance();
			try{
				storable.load(stream);
			} catch(EOFException e){
				//The storable has reached the end of the record data
				//We should ignore this because it could be that the data was saved in previous version of the storable
				//
				String s = System.getProperty("com.smssalama.storage.framework.Storable.dontIgnoreEOFException");
				if(s!=null && (s.endsWith("1") || s.equalsIgnoreCase("true"))){
					throw e;
				}
			}
			return storable;		
		} catch (IOException e) {
			e.printStackTrace();
			throw new ObjectStorageException(e.getMessage(), e);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			throw new ObjectStorageException("Could not find storable class '"
					+ className + "'", e);
		} catch (InstantiationException e) {
			e.printStackTrace();
			throw new ObjectStorageException(
					"Could not instantiate storable class '" + className + "'",
					e);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new ObjectStorageException(e.getMessage(), e);
		}
	}

	public int compareTo(Storable o) {
		return this.recordId - o.recordId;
	}

	public int compareTo(Storable o, Object criteria) {
		return this.recordId - o.recordId;
	}

	public static void dump(char type, int recordId, byte[] data) {
		StringBuffer b = new StringBuffer();
		for (int i = 0; i < data.length; i++) {
			b = b.append(Integer.toHexString(data[i]));
		}
		// System.out.println(type+":"+recordId+":"+b.toString());
	}
}
